#ifndef ENGINE_H
#define ENGINE_H

#include <QMutex>
#include <QThread>
#include <QList>
#include <QMap>
#include <QPair>

#include <time.h>

#include "anime.h"

#define OP_ADD_ANIMATION        0
#define OP_REMOVE_ANIMATION     1
#define OP_DELETE_ANIMATION     2
#define OP_ADD_GUI              3
#define OP_REMOVE_GUI           4
#define OP_ADD_TASK             5
#define OP_REMOVE_TASK          6

#define CLOCKS_PER_MS CLOCKS_PER_SEC/1000

namespace anime
{
    class Task
    {
    public:
        virtual void DoTask() = 0;
    };

    /* ------------------ AnimeThread ----------------------------------------- */
    class Engine: public QThread
    {
        Q_OBJECT

    public:
        void run()
        {
            qDebug("Engine started");

            long elapsed = 0, i = 0;
            QPair< int, void* > curOp;
            Animation* a = 0;
            Drawable* d = 0;
            Task* t = 0;

            clock_t t_start = 0;
            clock_t t_end = 0;
            t_start = clock();

            while(!stopIt)
            {
                draw_mutex->lock();
                // scheduled operations
                if( opsMutex.tryLock( ) )
                {
                    for( i=0; i<ops.size(); i++ )
                    {
                        curOp = ops.at( i );

                        switch( curOp.first )
                        {
                        case OP_ADD_ANIMATION:
                            a = ( Animation* )curOp.second;
                            animations.append( a );
                            break;
                        case OP_REMOVE_ANIMATION:
                            a = ( Animation* )curOp.second;
                            animations.removeOne( a );
                            break;
                        case OP_DELETE_ANIMATION:
                            a = ( Animation* )curOp.second;
                            animations.removeOne( a );
                            delete a;
                            break;
                        case OP_ADD_GUI:
                            d = ( Drawable* )curOp.second;
                            guis.append( d );
                            break;
                        case OP_REMOVE_GUI:
                            d = ( Drawable* )curOp.second;
                            guis.removeOne( d );
                            break;
                        case OP_ADD_TASK:
                            t = ( Task* )curOp.second;
                            tasks.append( t );
                            break;
                        case OP_REMOVE_TASK:
                            t = ( Task* )curOp.second;
                            tasks.removeOne( t );
                            break;
                        }
                    }
                    ops.clear();
                    opsMutex.unlock();
                }

                // the draw mutex is needed for thread safety
                //draw_mutex->lock();
                // animations tick
                for( i=0; i<animations.size(); i++ )
                {
                    animations.at( i )->Tick();
                }

                // tasks update
                for( i=0; i<tasks.size(); i++ )
                {
                    tasks.at( i )->DoTask();
                }
                //draw_mutex->unlock();

                // guis update
                for( i=0; i<guis.size(); i++ )
                {
                    guis.at( i )->Draw();
                }

                draw_mutex->unlock();

                t_end = clock();

                // is there available time? let's sleep...
                elapsed = t_end - t_start;
                if(elapsed < dt)
                {
                    msleep(dt - elapsed);
                }

                frame_time = elapsed;
                current_fps = ( (elapsed == 0)? fps : 1000/elapsed );
                t_start = clock();
            }
            qDebug("Engine terminated");
        }

        void StopIt() { stopIt = true; }

        int GetCurrentFPS( void )
        {
            return current_fps;
        }

        Animation* AddAnimation(Animation* a)
        {
            opsMutex.lock();
            ops.append( QPair< int, void* >( OP_ADD_ANIMATION, a ) );
            opsMutex.unlock();
            return a;
        }

        void RemoveAnimation(Animation* a, bool deleteIt = false)
        {
            opsMutex.lock();
            ops.append( QPair< int, void* >( deleteIt? OP_DELETE_ANIMATION : OP_REMOVE_ANIMATION, a ) );
            opsMutex.unlock();
        }

        void AddDrawable(Drawable* o)
        {
            opsMutex.lock();
            ops.append( QPair< int, void* >( OP_ADD_GUI, o ) );
            opsMutex.unlock();
        }

        void AddTask( Task* t )
        {
            opsMutex.lock();
            ops.append( QPair< int, void* >( OP_ADD_TASK, t ) );
            opsMutex.unlock();
        }

        void RemoveTask( Task* t )
        {
            opsMutex.lock();
            ops.append( QPair< int, void* >( OP_REMOVE_TASK, t ) );
            opsMutex.unlock();
        }

        bool RemoveDrawable(Drawable* o)
        {
            opsMutex.lock();
            ops.append( QPair< int, void* >( OP_REMOVE_GUI, o ) );
            opsMutex.unlock();
            return true;
        }

        void PrintState( void )
        {
            qDebug( "------------ engine state --------------- ");
            qDebug( "Running: %s", this->isRunning()? "true" : "false" );
            qDebug( "Animations: %d", animations.size() );
            qDebug( "Tasks: %d", tasks.size() );
            qDebug( "Guis: %d", guis.size() );
        }

        Engine(int _fps, QMutex* _draw_mutex):fps(_fps), draw_mutex( _draw_mutex )
        {
            dt = 1000/fps;
            stopIt = false;
            current_fps = fps;
        }

        ~Engine()
        { 
            StopIt();
            while(isRunning());
        }

        int const fps;
        int current_fps;
        int frame_time;

    private:
        int dt;
        bool stopIt;
        QList< Animation* > animations;
        QList< Drawable* > guis;
        QList< Task* > tasks;
        QList< QPair< int, void* > > ops;
        QMutex opsMutex;
        QMutex* draw_mutex;
    };
    /* ------------------------------------------------------------------------ */
}

#endif
